package com.jijunpeng.javafx.cipher.controller;

import cn.hutool.core.io.IoUtil;
import cn.hutool.crypto.BCUtil;
import cn.hutool.crypto.GlobalBouncyCastleProvider;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import com.jijunpeng.javafx.cipher.common.DialogUtil;
import com.jijunpeng.javafx.cipher.common.enums.*;
import com.jijunpeng.javafx.cipher.controller.api.BaseController;
import com.jijunpeng.javafx.cipher.controller.api.ICipherController;
import com.jijunpeng.javafx.cipher.exception.KeyTypeException;
import com.jijunpeng.javafx.cipher.persistence.PersistenceJson;
import javafx.collections.FXCollections;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.Button;
import javafx.scene.control.ComboBox;
import javafx.scene.control.RadioButton;
import javafx.scene.control.TextArea;
import javafx.scene.layout.Pane;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.DecoderException;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;
import org.bouncycastle.util.io.pem.PemObject;

import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;

/**
 * @author Ji Junpeng
 * @date 2019-11-30 19-45
 */
@Getter
@Slf4j
@PersistenceJson(path = "$.main_inner_$rsa")
public class RsaController extends BaseController implements ICipherController {
    @FXML
    private Pane rsaRootPane;
    @FXML
    @PersistenceJson(path = "leftHexRb", field = "selected", getter = "isSelected")
    private RadioButton leftHexRb;
    @FXML
    @PersistenceJson(path = "leftBase64Rb", field = "selected", getter = "isSelected")
    private RadioButton leftBase64Rb;
    @FXML
    @PersistenceJson(path = "leftStringRb", field = "selected", getter = "isSelected")
    private RadioButton leftStringRb;
    @FXML
    @PersistenceJson(path = "leftStringCharsetComboBox", field = "value")
    private ComboBox<CharsetEnum> leftStringCharsetComboBox;
    @FXML
    @PersistenceJson(path = "leftTextArea", field = "text")
    private TextArea leftTextArea;
    @FXML
    @PersistenceJson(path = "rightHexRb", field = "selected", getter = "isSelected")
    private RadioButton rightHexRb;
    @FXML
    @PersistenceJson(path = "rightBase64Rb", field = "selected", getter = "isSelected")
    private RadioButton rightBase64Rb;
    @FXML
    @PersistenceJson(path = "rightTextArea", field = "text")
    private TextArea rightTextArea;
    @FXML
    @PersistenceJson(path = "paddingComboBox", field = "value")
    private ComboBox<RsaPaddingEnum> paddingComboBox;
    @FXML
    @PersistenceJson(path = "cipherProviderComboBox", field = "value")
    private ComboBox<CipherProviderEnum> cipherProviderComboBox;
    @FXML
    @PersistenceJson(path = "privateKeyTypeComboBox", field = "value")
    private ComboBox<RsaPrivateKeyTypeEnum> privateKeyTypeComboBox;
    @FXML
    @PersistenceJson(path = "publicKeyTypeComboBox", field = "value")
    private ComboBox<RsaPublicKeyTypeEnum> publicKeyTypeComboBox;
    @FXML
    @PersistenceJson(path = "privateKeyTextArea", field = "text")
    private TextArea privateKeyTextArea;
    @FXML
    @PersistenceJson(path = "publicKeyTextArea", field = "text")
    private TextArea publicKeyTextArea;
    @FXML
    private Pane paddingVg;
    @FXML
    private Button genKeyPairBtn;
    @FXML
    private Button genPublicKeyBtn;

    @Override
    protected Pane getRootPane() {
        return rsaRootPane;
    }

    @Override
    protected void initViewData() {
        super.initViewData();
        initCipherController();

        paddingComboBox.setItems(FXCollections.observableArrayList(RsaPaddingEnum.values()));
        paddingComboBox.setValue(RsaPaddingEnum.values()[0]);

        // todo
        privateKeyTypeComboBox.setItems(FXCollections.singletonObservableList(RsaPrivateKeyTypeEnum.values()[0]));
        privateKeyTypeComboBox.setValue(RsaPrivateKeyTypeEnum.values()[0]);

        publicKeyTypeComboBox.setItems(FXCollections.observableArrayList(RsaPublicKeyTypeEnum.values()));
        publicKeyTypeComboBox.setValue(RsaPublicKeyTypeEnum.values()[0]);

        privateKeyTextArea.setPromptText("-----BEGIN PRIVATE KEY----- 开头，或者 -----BEGIN RSA PRIVATE KEY----- 开头");
        publicKeyTextArea.setPromptText("-----BEGIN PUBLIC KEY----- 开头");

        // todo
        paddingVg.setVisible(false);
        publicKeyTypeComboBox.setVisible(false);
        genKeyPairBtn.setVisible(false);
        genPublicKeyBtn.setVisible(false);
    }

    /**
     * left --private key encrypt--> right
     */
    @FXML
    private void encryptByPrivateKey(ActionEvent actionEvent) {
        try {
            PrivateKey privateKey = getPrivateKey();
            byte[] inputBytes = getLeftBytes();
            byte[] outputBytes = newRsa().setPrivateKey(privateKey).encrypt(inputBytes, KeyType.PrivateKey);
            String rightStr = buildRightStr(outputBytes);
            rightTextArea.setText(rightStr);
            log.info("RSA encrypt by private key. inputStr:{}, outputStr:{}", leftTextArea.getText(), rightStr);
        } catch (Exception e) {
            log.error("RSA encrypt by private key throw exception. e:", e);
            DialogUtil.showError(rsaRootPane, "RSA 私钥加密失败", "错误原因：" + e.getMessage());
        }
    }

    /**
     * left --public key encrypt--> right
     */
    @FXML
    private void encryptByPublicKey(ActionEvent actionEvent) {
        try {
            PublicKey publicKey = getPublicKey();
            byte[] inputBytes = getLeftBytes();
            byte[] outputBytes = newRsa().setPublicKey(publicKey).encrypt(inputBytes, KeyType.PublicKey);
            String rightStr = buildRightStr(outputBytes);
            rightTextArea.setText(rightStr);
            log.info("RSA encrypt by public key. inputStr:{}, outputStr:{}", leftTextArea.getText(), rightStr);
        } catch (Exception e) {
            log.error("RSA encrypt by public key throw exception. e:", e);
            DialogUtil.showError(rsaRootPane, "RSA 私钥加密失败", "错误原因：" + e.getMessage());
        }
    }

    /**
     * left <--private key decrypt-- right
     */
    @FXML
    private void decryptByPrivateKey(ActionEvent actionEvent) {
        try {
            PrivateKey privateKey = getPrivateKey();
            byte[] inputBytes = getRightBytes();
            byte[] outputBytes = newRsa().setPrivateKey(privateKey).decrypt(inputBytes, KeyType.PrivateKey);
            String leftStr = buildLeftStr(outputBytes);
            leftTextArea.setText(leftStr);
            log.info("RSA decrypt by private key. inputStr:{}, outputStr:{}", rightTextArea.getText(), leftStr);
        } catch (Exception e) {
            log.error("RSA decrypt by private key throw exception. e:", e);
            DialogUtil.showError(rsaRootPane, "RSA 私钥解密失败", "错误原因：" + e.getMessage());
        }
    }

    /**
     * left <--public key decrypt-- right
     */
    @FXML
    private void decryptByPublicKey(ActionEvent actionEvent) {
        try {
            PublicKey publicKey = getPublicKey();
            byte[] inputBytes = getRightBytes();
            byte[] outputBytes = newRsa().setPublicKey(publicKey).decrypt(inputBytes, KeyType.PublicKey);
            String leftStr = buildLeftStr(outputBytes);
            leftTextArea.setText(leftStr);
            log.info("RSA decrypt by public key. inputStr:{}, outputStr:{}", rightTextArea.getText(), leftStr);
        } catch (Exception e) {
            log.error("RSA decrypt by public key throw exception. e:", e);
            DialogUtil.showError(rsaRootPane, "RSA 公钥解密失败", "错误原因：" + e.getMessage());
        }
    }

    private RSA newRsa() {
        GlobalBouncyCastleProvider.setUseBouncyCastle(cipherProviderComboBox.getValue() == CipherProviderEnum.BOUNCY_CASTLE);
        return new RSA();
    }

    /**
     * 自动成成秘钥对
     */
    @FXML
    private void genKeyPair(ActionEvent actionEvent) {
        System.out.println("生成秘钥对");
    }

    /**
     * 根据私钥生成公钥
     */
    @FXML
    private void genPublicKey(ActionEvent actionEvent) {
        System.out.println("私钥转公钥");
    }

    private PrivateKey getPrivateKey() throws KeyTypeException, DecoderException {
        String inputStr = privateKeyTextArea.getText();
        PrivateKey privateKey;
        switch (privateKeyTypeComboBox.getValue()) {
            case PKCS8_OR_PKCS1:
                privateKey = BCUtil.readPrivateKey(IoUtil.toUtf8Stream(inputStr));
                break;
            case BASE64:
                privateKey = BCUtil.readPrivateKey(IoUtil.toStream(Base64.decodeBase64(inputStr)));
                break;
            case HEX:
                privateKey = BCUtil.readPrivateKey(IoUtil.toStream(Hex.decodeHex(inputStr)));
                break;
            default:
                throw new KeyTypeException("私钥类型错误");
        }
        return privateKey;
    }

    public PublicKey getPublicKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        String inputStr = publicKeyTextArea.getText();
//        PublicKey publicKey = BCUtil.readPublicKey(IoUtil.toUtf8Stream(inputStr));
        PemObject pemObject = BCUtil.readPemObject(IoUtil.toUtf8Stream(inputStr));
        X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(pemObject.getContent());
        PublicKey publicKey = KeyFactory.getInstance("RSA").generatePublic(x509EncodedKeySpec);
        return publicKey;
    }
}
